package com.example.framework.security.core.filter;

import cn.hutool.core.util.ObjectUtil;
import com.example.framework.common.pojo.R;
import com.example.framework.common.util.servlet.ServletUtils;
import com.example.framework.security.config.SecurityProperties;
import com.example.framework.security.core.LoginUser;
import com.example.framework.security.core.util.JwtTokenUtils;
import com.example.framework.security.core.util.SecurityFrameworkUtils;
import com.example.framework.web.core.handler.GlobalExceptionHandler;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.resource.NoResourceFoundException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * Token 过滤器，验证 token 的有效性
 * 验证通过后，获得 {@link LoginUser} 信息，并加入到 Spring Security 上下文
 * 没有 Token 则放行，
 * TokenAuthenticationFilter -> Spring Security Filter                            -> HandlerInterceptor -> Aspect -> Controller -> {@link BasicErrorController}处理/error，即常见的500错误页
 * |_ 抛异常转为/error继续        |_ 被拦截，进入 {@link AuthenticationEntryPoint}      |____________________|__________|__________-> 抛异常，被{@link GlobalExceptionHandler}处理（如果path不存在，抛出{@link NoResourceFoundException}）
 */
@Slf4j
@RequiredArgsConstructor
public class TokenAuthenticationFilter extends OncePerRequestFilter {

    private final SecurityProperties securityProperties;

    private final GlobalExceptionHandler globalExceptionHandler;

    private final JwtTokenUtils jwtTokenUtils;

    @Override
    protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
        log.debug("---------- TokenAuthenticationFilter.doFilterInternal, request url: {} ----------", request.getRequestURL());
        String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getHeaderName());
        if (ObjectUtil.isNotEmpty(token)) {
            try {
                // 1.1 基于 token 构建登录用户
                LoginUser loginUser = buildLoginUserByToken(token);

                // 2. 设置当前用户
                SecurityFrameworkUtils.setLoginUser(loginUser, request);
            } catch (Throwable ex) {
                R<?> result = globalExceptionHandler.allExceptionHandler(ex);
                ServletUtils.writeJSON(response, result);
                return;
            }
        }

        // 继续过滤链
        filterChain.doFilter(request, response);
    }

    private LoginUser buildLoginUserByToken(String token) {
        // 模拟登录用户，方便日常开发调试
        if (Boolean.TRUE.equals(securityProperties.getMockEnable()) && Objects.equals(securityProperties.getMockSecret(), token)) {
            return new LoginUser()
                    .setUsername("mock")
                    .setNickname("mock账号")
                    .setPermissions(new ArrayList<>(List.of("admin")));
        }
        jwtTokenUtils.checkAccessToken(token);
        return jwtTokenUtils.getUserInfoFromCacheByToken(token);
    }
}
